home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue63 / Alfresco / AAExtMge.pas next >
Encoding:
Pascal/Delphi Source File  |  2000-10-08  |  14.0 KB  |  439 lines

  1. {*********************************************************}
  2. {* AAExtMge                                              *}
  3. {* Copyright (c) Julian M Bucknall 2000                  *}
  4. {* All rights reserved.                                  *}
  5. {*********************************************************}
  6. {* Algorithms Alfresco: External Mergesort               *}
  7. {*********************************************************}
  8.  
  9. {Note: this unit is released as freeware. In other words, you are free
  10.        to use this unit in your own applications, however I retain all
  11.        copyright to the code. JMB}
  12.  
  13. unit AAExtMge;
  14.  
  15. interface
  16.  
  17. uses
  18.   Windows,
  19.   SysUtils;
  20.  
  21. type
  22.   {function prototype to compare two items;
  23.    returns integer <0 if Item1<Item2, =0 if equal, >0 otherwise}
  24.   TaaMergeCompare = function (const aItem1, aItem2 : pointer) : integer;
  25.  
  26.  
  27. procedure aaMergesortFixed(const aInFile  : string;
  28.                            const aOutFile : string;
  29.                                  aRecLen  : integer;
  30.                                  aCompare : TaaMergeCompare);
  31.   {-mergesorts the input file to produce the output file; the input
  32.     file is assumed to contain fixed length records of size aRecLen}
  33.  
  34. implementation
  35.  
  36. uses
  37.   Classes;
  38.  
  39. {===TaaTempFileStream================================================}
  40. type
  41.   TaaTempFileStream = class(TFileStream)
  42.     private
  43.       FFileName : string;
  44.       FDelete   : boolean;
  45.     protected
  46.     public
  47.       constructor Create(const aPath : string; aMode : word);
  48.       destructor Destroy; override;
  49.  
  50.       property DeleteOnDestroy : boolean read FDelete write FDelete;
  51.       property FileName : string read FFileName;
  52.   end;
  53. {--------}
  54. constructor TaaTempFileStream.Create(const aPath : string; aMode : word);
  55. var
  56.   PathNameZ : array [0..MAX_PATH] of char;
  57.   FileNameZ : array [0..MAX_PATH] of char;
  58. begin
  59.   {get the path for temporary files}
  60.   if (aPath = '') then
  61.     GetTempPath(sizeof(PathNameZ), PathNameZ)
  62.   else
  63.     StrLCopy(PathNameZ, PChar(aPath), sizeof(PathNameZ));
  64.   {create a temporary file}
  65.   GetTempFileName(PathNameZ, 'AA', 0, FileNameZ);
  66.   FFileName := FileNameZ;
  67.   {this last step will have created the file, so open it}
  68.   inherited Create(FileName, aMode);
  69. end;
  70. {--------}
  71. destructor TaaTempFileStream.Destroy;
  72. begin
  73.   {close the file}
  74.   inherited Destroy;
  75.   {if we're asked to delete the file, do so}
  76.   if DeleteOnDestroy then
  77.     DeleteFile(FileName);
  78. end;
  79. {====================================================================}
  80.  
  81.  
  82. {====================================================================}
  83. function ReadRecFixed(aStream : TStream;
  84.                   var aBuffer;
  85.                       aRecLen : integer) : boolean;
  86. var
  87.   BytesRead : longint;
  88. begin
  89.   BytesRead := aStream.Read(aBuffer, aRecLen);
  90.   Result := BytesRead = aRecLen;
  91. end;
  92. {--------}
  93. procedure SelectionSort(aBlock    : pointer;
  94.                         aRecCount : integer;
  95.                         aRecLen   : integer;
  96.                         aCompare  : TaaMergeCompare);
  97. var
  98.   i, j    : integer;
  99.   TempRec : pointer;
  100.   iPtr    : PChar;
  101.   jPtr    : PChar;
  102.   MinPtr  : PChar;
  103. begin
  104.   GetMem(TempRec, aRecLen);
  105.   try
  106.     iPtr := aBlock;
  107.     for i := 0 to (aRecCount - 2) do begin
  108.       MinPtr := iPtr;
  109.       jPtr := iPtr;
  110.       for j := succ(i) to pred(aRecCount) do begin
  111.         inc(jPtr, aRecLen);
  112.         if (aCompare(jPtr, MinPtr) < 0) then
  113.           MinPtr := jPtr;
  114.       end;
  115.       Move(iPtr^, TempRec^, aRecLen);
  116.       Move(MinPtr^, iPtr^, aRecLen);
  117.       Move(TempRec^, MinPtr^, aRecLen);
  118.       inc(iPtr, aRecLen);
  119.     end;
  120.   finally
  121.     FreeMem(TempRec);
  122.   end;
  123. end;
  124. {--------}
  125. procedure SplitFileFixed(aInFile : TStream;
  126.                          aF1     : TStream;
  127.                          aF2     : TStream;
  128.                          aRecLen : integer;
  129.                          aCompare: TaaMergeCompare);
  130. const
  131.   FirstFile = false;
  132.   SecondFile = true;
  133. var
  134.   Rec1 : pointer;
  135.   Rec2 : pointer;
  136.   F    : array [boolean] of TStream;
  137.   Have1st : boolean;
  138.   Have2nd : boolean;
  139.   Use1st1st : boolean;
  140.   DestFile  : boolean;
  141. begin
  142.   F[FirstFile] := aF1;
  143.   F[SecondFile] := aF2;
  144.   Rec1 := nil;
  145.   Rec2 := nil;
  146.   try
  147.     {allocate the record buffers}
  148.     GetMem(Rec1, aRecLen);
  149.     GetMem(Rec2, aRecLen);
  150.     {we start out with the first output file}
  151.     DestFile := FirstFile;
  152.     {read the first two records}
  153.     Have1st := ReadRecFixed(aInFile, Rec1^, aRecLen);
  154.     Have2nd := ReadRecFixed(aInFile, Rec2^, aRecLen);
  155.     {in a loop read the records in pairs and write them in sequence to
  156.      the output file, alternating between output files; the loop stops
  157.      when we can't read any more records}
  158.     while Have1st do begin
  159.       {order the two records}
  160.       if Have2nd then
  161.         Use1st1st := aCompare(Rec1, Rec2) <= 0
  162.       else
  163.         Use1st1st := true;
  164.       {write them out in order to the current output file}
  165.       if Use1st1st then begin
  166.         F[DestFile].WriteBuffer(Rec1^, aRecLen);
  167.         if Have2nd then
  168.           F[DestFile].WriteBuffer(Rec2^, aRecLen);
  169.       end
  170.       else begin
  171.         F[DestFile].WriteBuffer(Rec2^, aRecLen);
  172.         F[DestFile].WriteBuffer(Rec1^, aRecLen);
  173.       end;
  174.       {switch output files}
  175.       DestFile := not DestFile;
  176.       {read the next two records}
  177.       Have1st := ReadRecFixed(aInFile, Rec1^, aRecLen);
  178.       Have2nd := ReadRecFixed(aInFile, Rec2^, aRecLen);
  179.     end;
  180.   finally
  181.     if (Rec2 <> nil) then
  182.       FreeMem(Rec2);
  183.     if (Rec1 <> nil) then
  184.       FreeMem(Rec1);
  185.   end;
  186. end;
  187. {--------}
  188. function SplitFileFixedBlock(aInFile : TStream;
  189.                              aF1     : TStream;
  190.                              aF2     : TStream;
  191.                              aRecLen : integer;
  192.                              aCompare: TaaMergeCompare) : integer;
  193. const
  194.   FirstFile = false;
  195.   SecondFile = true;
  196. var
  197.   Block : pointer;
  198.   F    : array [boolean] of TStream;
  199.   DestFile  : boolean;
  200.   BlockSize : integer;
  201.   BytesRead   : longint;
  202.   RecCount    : integer;
  203. begin
  204.   F[FirstFile] := aF1;
  205.   F[SecondFile] := aF2;
  206.   Block := nil;
  207.   try
  208.     {allocate the block buffer}
  209.     Result := (128 * 1024) div aRecLen;
  210.     BlockSize := Result * aRecLen;
  211.     GetMem(Block, BlockSize);
  212.     {we start out with the first output file}
  213.     DestFile := FirstFile;
  214.     {read the first block}
  215.     BytesRead := aInFile.Read(Block^, BlockSize);
  216.     RecCount := BytesRead div aRecLen;
  217.     {in a loop sort the block and write it to the output file,
  218.      alternating between output files; the loop stops when we can't
  219.      read any more blocks}
  220.     while (RecCount <> 0) do begin
  221.       {sort the block}
  222.       SelectionSort(Block, RecCount, aRecLen, aCompare);
  223.       {write out the sorted block to the current output file}
  224.       F[DestFile].WriteBuffer(Block^, BytesRead);
  225.       {switch output files}
  226.       DestFile := not DestFile;
  227.       {read the next block}
  228.       BytesRead := aInFile.Read(Block^, BlockSize);
  229.       RecCount := BytesRead div aRecLen;
  230.     end;
  231.   finally
  232.     if (Block <> nil) then
  233.       FreeMem(Block);
  234.   end;
  235. end;
  236. {--------}
  237. function MergeRunsFixed(aF1     : TStream;
  238.                         aF2     : TStream;
  239.                         aG1     : TStream;
  240.                         aG2     : TStream;
  241.                         aRecLen : integer;
  242.                         aRunLen : integer;
  243.                         aCompare: TaaMergeCompare) : boolean;
  244. const
  245.   FirstFile = false;
  246.   SecondFile = true;
  247. type
  248.   {a record that describes the processing of a single input file}
  249.   TInputFile = packed record
  250.     ifStrm      : TStream; {stream}
  251.     ifRec       : pointer; {record buffer}
  252.     ifRecsInRun : integer; {records to go in run}
  253.     ifEOF       : boolean; {stream is exhausted}
  254.   end;
  255. var
  256.   F : array[boolean] of TInputFile;
  257.   G : array [boolean] of TStream;
  258.   SrcFile   : boolean;
  259.   DestFile  : boolean;
  260.   FileId    : boolean;
  261. begin
  262.   {assume that this merge pass will finish completely}
  263.   Result := true;
  264.   {initialize the input file records}
  265.   with F[FirstFile] do begin
  266.     ifStrm := aF1;
  267.     ifRec := nil;
  268.     ifRecsInRun := 0;
  269.     ifEOF := false;
  270.   end;
  271.   with F[SecondFile] do begin
  272.     ifStrm := aF2;
  273.     ifRec := nil;
  274.     ifRecsInRun := 0;
  275.     ifEOF := false;
  276.   end;
  277.   {set up the output files}
  278.   G[FirstFile] := aG1;
  279.   G[SecondFile] := aG2;
  280.   try
  281.     {clear the output streams}
  282.     {NOTE: this only works for Delphi 3 and above, since only
  283.            their TStreams have a SetSize accessor method}
  284.     G[FirstFile].Size := 0;
  285.     G[SecondFile].Size := 0;
  286.     {reset the input streams, allocate the record buffers,
  287.      and set the EOF flags}
  288.     for FileId := FirstFile to SecondFile do
  289.       with F[FileId] do begin
  290.         ifStrm.Seek(0, soFromBeginning);
  291.         GetMem(ifRec, aRecLen);
  292.         ifEOF := ifStrm.Size = 0;
  293.       end;
  294.     {make sure the first output goes to G1}
  295.     DestFile := FirstFile;
  296.     {cycle until we manage to exhaust both input files}
  297.     while (not F[FirstFile].ifEOF) or
  298.           (not F[SecondFile].ifEOF) do begin
  299.       {if we start writing to the second file, we won't finish
  300.        the merge process this time}
  301.       if (DestFile = SecondFile) then
  302.         Result := false;
  303.       {initialize ready for merging next runs}
  304.       F[FirstFile].ifRecsInRun := aRunLen;
  305.       F[SecondFile].ifRecsInRun := aRunLen;
  306.       {read the first two records in the respective runs}
  307.       with F[FirstFile] do
  308.         if ReadRecFixed(ifStrm, ifRec^, aRecLen) then
  309.           dec(ifRecsInRun)
  310.         else begin
  311.           ifRecsInRun := -1;
  312.           ifEOF := true;
  313.         end;
  314.       with F[SecondFile] do
  315.         if ReadRecFixed(ifStrm, ifRec^, aRecLen) then
  316.           dec(ifRecsInRun)
  317.         else begin
  318.           ifRecsInRun := -1;
  319.           ifEOF := true;
  320.         end;
  321.       {merge the two runs--one from F1 and the other from F2}
  322.       while ((F[FirstFile].ifRecsInRun >= 0) or
  323.              (F[SecondFile].ifRecsInRun >= 0)) do begin
  324.         {find the smaller record of the two current ones}
  325.         {if the run from F1 is exhausted then the record from
  326.          F2 is the 'smaller'}
  327.         if (F[FirstFile].ifRecsInRun < 0) then
  328.           SrcFile := SecondFile
  329.         {if the run from F2 is exhausted then the record from
  330.          F1 is the 'smaller'}
  331.         else if (F[SecondFile].ifRecsInRun < 0) then
  332.           SrcFile := FirstFile
  333.         {otherwise we need to actually compare the records to
  334.          find the smaller}
  335.         else
  336.           SrcFile :=
  337.              aCompare(F[FirstFile].ifRec, F[SecondFile].ifRec) > 0;
  338.         {write the smaller record to the current output file}
  339.         G[DestFile].WriteBuffer(F[SrcFile].ifRec^, aRecLen);
  340.         {read the next record from the file whose record we just used}
  341.         with F[SrcFile] do
  342.           if (ifRecsInRun <= 0) then
  343.             ifRecsInRun := -1
  344.           else if ReadRecFixed(ifStrm, ifRec^, aRecLen) then
  345.             dec(ifRecsInRun)
  346.           else begin
  347.             ifRecsInRun := -1;
  348.             ifEOF := true;
  349.           end
  350.       end;
  351.       {having merged two runs, switch output files}
  352.       DestFile := not DestFile;
  353.     end;
  354.   finally
  355.     if (F[SecondFile].ifRec <> nil) then
  356.       FreeMem(F[SecondFile].ifRec);
  357.     if (F[FirstFile].ifRec <> nil) then
  358.       FreeMem(F[FirstFile].ifRec);
  359.   end;
  360. end;
  361. {--------}
  362. procedure aaMergesortFixed(const aInFile  : string;
  363.                            const aOutFile : string;
  364.                                  aRecLen  : integer;
  365.                                  aCompare : TaaMergeCompare);
  366. var
  367.   InFile : TFileStream;
  368.   F      : array [1..2] of TaaTempFileStream;
  369.   G      : array [1..2] of TaaTempFileStream;
  370.   Merged : boolean;
  371.   FIsSrc : boolean;
  372.   RunLen : integer;
  373.   Path   : string;
  374.   MergedFileName : string;
  375. begin
  376.   Assert(aInFile <> '', 'Input file name cannot be blank');
  377.   Assert(aOutFile <> '', 'Output file name cannot be blank');
  378.   Assert(aRecLen > 0, 'Record length must be a positive number');
  379.   Assert(Assigned(aCompare), 'Compare function must be set');
  380.   InFile := nil;
  381.   F[1] := nil;
  382.   F[2] := nil;
  383.   G[1] := nil;
  384.   G[2] := nil;
  385.   try
  386.     {open the file to be sorted}
  387.     InFile := TFileStream.Create(aInFile, fmOpenRead+fmShareDenyWrite);
  388.     {split the file into the first two file components}
  389.     Path := ExtractFilePath(aOutFile);
  390.     {split the input file into two files containing runs of length 2}
  391.     F[1] := TaaTempFileStream.Create(Path, fmOpenReadWrite);
  392.     F[2] := TaaTempFileStream.Create(Path, fmOpenReadWrite);
  393. //  SplitFileFixed(InFile, F[1], F[2], aRecLen, aCompare);
  394. //  RunLen := 2;
  395.     RunLen := SplitFileFixedBlock(InFile, F[1], F[2], aRecLen, aCompare);
  396.     {perform the first merge pass}
  397.     G[1] := TaaTempFileStream.Create(Path, fmOpenReadWrite);
  398.     G[2] := TaaTempFileStream.Create(Path, fmOpenReadWrite);
  399.     Merged := MergeRunsFixed(F[1], F[2], G[1], G[2],
  400.                              aRecLen, RunLen, aCompare);
  401.     {now we continually merge the runs until we end up with a single
  402.      file containing all the records}
  403.     FIsSrc := true;
  404.     while not Merged do begin
  405.       RunLen := RunLen * 2;
  406.       FIsSrc := not FIsSrc;
  407.       if FIsSrc then
  408.         Merged := MergeRunsFixed(F[1], F[2], G[1], G[2],
  409.                                  aRecLen, RunLen, aCompare)
  410.       else
  411.         Merged := MergeRunsFixed(G[1], G[2], F[1], F[2],
  412.                                  aRecLen, RunLen, aCompare);
  413.     end;
  414.     {we've now merged all the records into either F1 or G1; rename the
  415.      file containing all the records to the output file name, and
  416.      then delete the other three temporaries}
  417.     if FIsSrc then begin
  418.       MergedFileName := G[1].FileName;
  419.       F[1].DeleteOnDestroy := true;
  420.     end
  421.     else begin
  422.       MergedFileName := F[1].FileName;
  423.       G[1].DeleteOnDestroy := true;
  424.     end;
  425.     F[2].DeleteOnDestroy := true;
  426.     G[2].DeleteOnDestroy := true;
  427.   finally
  428.     G[2].Free;
  429.     G[1].Free;
  430.     F[2].Free;
  431.     F[1].Free;
  432.     InFile.Free;
  433.   end;
  434.   RenameFile(MergedFileName, aOutFile);
  435. end;
  436.  
  437. end.
  438.  
  439.